#ifndef CPP_TIMER_HPP
#define CPP_TIMER_HPP

#include <chrono>
#include <atomic>
#include <future>
#include <condition_variable>

class Timer
{
public:
    Timer() = default;

    ~Timer()
    {
        stop();
    }

    /**
     * \brief Starts or restarts the timer with a timeout of \a duration and a function of \a task.
     * 
     * If the timer is already running, it will be stopped and restarted.
     * If \a once is true, the timer will be activaed only once.
     */
    template<class Rep, class Period>
    void start(const std::chrono::duration<Rep, Period> duration, const std::function<void()>& task, bool once = false)
    {
        if (is_running()) {
            stop();
            return;
        }
        this->running_ = true;
        future_ = std::async(std::launch::async, [this, &duration, &task, once] {
            while (this->running_) {
                std::unique_lock<std::mutex> lock(this->mutex_);
                const auto status = cv_.wait_for(lock, duration);
                if (status == std::cv_status::timeout) {
                    task();
                }
                if (once) {
                    this->running_ = false;
                    break;
                }
            }
        });
    }

    /**
     * \brief Starts or restarts the timer with a timeout of \a duration and a function of \a task once.
     * 
     * If the timer is already running, it will be stopped and restarted.
     * The timer will be activaed only once.
     */
    template<class Rep, class Period>
    void start_once(const std::chrono::duration<Rep, Period>& duration, const std::function<void()>& task)
    {
        this->start<Rep, Period>(duration, task, true);
    }

    /**
     * \brief Stops the timer.
     * \sa start()
     */
    void stop()
    {
        if (!this->running_) {
            return;
        }
        {
            std::unique_lock<std::mutex> lock(mutex_);
            this->running_ = false;
        }
        cv_.notify_one();
        future_.wait();
    }

    /**
     * Returns true if the timer is running (pending); otherwise returns false.
     */
    bool is_running() const
    {
        return running_;
    }

private:
    std::future<void> future_;
    std::atomic_bool running_{ false };
    std::mutex mutex_;
    std::condition_variable cv_;
};

#endif // CPP_TIMER_HPP